
from math import atan, degrees

from OpenGL.GLU import *
from OpenGL.GL import *
import numpy


class Camera(object):
    '''
    This class defines basic camera operations and fields that 
    represent camera's properties.
    '''


    def __init__(self, width, height):
        '''
        Constructor.
        '''
        self.lens = {'fovy':40.0, 'near':1.0, 'far':100.0, 'aspect':float(width)/height}
        self.position = [0.0, 0.0, 10.0]
        self.up_vector = [0.0, 1.0, 0.0]
        self.aim = [0.0, 0.0, 0.0]
        self.rotation_matrix = numpy.identity(4) 
        
    def look_at(self):
        '''
        Moves and aims the camera.
        '''
        glMatrixMode(GL_MODELVIEW)
        glLoadIdentity()
        gluLookAt( *(self.position + self.aim + self.up_vector) )
        glMultMatrixd(self.rotation_matrix)

    def update_lens_aspect(self, width, height):
        '''
        Updates the lens' aspect. Must be called after window resizing.
        '''
        self.lens['aspect'] = float(width)/height
        self.reset_lens()
        self.adjust_lens()
        
    def adjust_lens(self):
        '''
        Adjusts the camera's lens.
        '''
        glMatrixMode(GL_PROJECTION)
        gluPerspective(self.lens['fovy'],
                       self.lens['aspect'],
                       self.lens['near'],
                       self.lens['far'])

    def move(self, var):
        '''
        Moves all objects in the scene changing the camera's
        position and aim.
        '''
        for i in range(2):
            var[i] *= self.movement_multiplier()
            self.position[i] += var[i]
            self.aim[i] += var[i]
        
        self.look_at()
    
    def zoom(self, var):
        '''
        Zooms in and out varying the fovy angle of the lens.
        '''
        var *= 0.3
        new_fovy = self.lens['fovy'] - var
        
        if new_fovy > 0.0 and new_fovy < 180.0:
            self.lens['fovy'] = new_fovy

        self.reset_lens()
        self.adjust_lens()
        
    def reset_lens(self):
        '''
        Resets camera's lens.
        '''
        glMatrixMode(GL_PROJECTION)
        glLoadIdentity()
    
    def center_scene(self, box):
        '''
        Centers the camera, showing all objects in the scene.
        '''
        x = box[1] - box[0]  # x max - x min
        y = box[3] - box[2]  # y max - y min

        box_center = [(box[0] + box[1]) / 2,
                      (box[2] + box[3]) / 2,
                      (box[4] + box[5]) / 2]
        
        self.aim = box_center
        self.position = [box_center[0],
                         box_center[1],
                         box[5] + 8.0]
        
        self.lens['near'] = min(1.0, self.position[2] - box[5])
        self.lens['far'] = max(100.0, self.position[2] - box[4])
        
        if y == 0: 
            y = 0.01
        box_aspect = x/y
        d = self.position[2] - box[5]
        
        if box_aspect < self.lens['aspect']:
            self.lens['fovy'] = degrees(atan(y / 2 / d)) * 2
        else:
            self.lens['fovy'] = degrees(atan(x / 2 / d / self.lens['aspect'])) * 2
        
        self.rotation_matrix = numpy.identity(4)
        
        self.reset_lens()
        self.adjust_lens()
        self.look_at()
    
    def reset(self):
        '''
        Resets the camera.
        '''
        self.reset_lens()
        self.lens['near'] = 1.0
        self.lens['far'] = 100.0
        self.lens['fovy'] = 40.0        
        self.adjust_lens()
        self.rotation_matrix = numpy.identity(4)
        self.look_at()

    
    def movement_multiplier(self):
        '''
        Returns a multiplier that makes a movement proportional to the
        change in the fovy angle of the camera's lens (zoom).
        '''
        return 0.005 * self.lens['fovy'] / 30.0
        
